package com.yami.shop.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.yami.shop.bean.enums.FlowLogPageEnum;
import com.yami.shop.bean.enums.FlowVisitEnum;
import com.yami.shop.bean.model.FlowLog;
import com.yami.shop.bean.param.ProdAnalysisDataParam;
import com.yami.shop.bean.param.ProdAnalysisSurveyParam;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.util.CacheManagerUtil;
import com.yami.shop.dao.FlowLogMapper;
import com.yami.shop.service.FlowLogService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 用户流量记录
 *
 * @author YXF
 * @date 2020-07-13 13:18:33
 */
@Service
@Slf4j
@AllArgsConstructor
public class FlowLogServiceImpl extends ServiceImpl<FlowLogMapper, FlowLog> implements FlowLogService {

    private final FlowLogMapper flowLogMapper;

    private final CacheManagerUtil cacheManagerUtil;

    private final RedisTemplate redisTemplate;

    private final String CACHE_NAME = "FlowAnalysis";
    private final String CACHE_KEY = "flowLogList";

    public static final String UNKNOWN = "unknown";
    public static final String LOCALHOST = "127.0.0.1";
    public static final Pattern PATTERN = Pattern.compile("[0-9]*");

    @Override
    public Date getMinCreateTime() {
        return flowLogMapper.getMinCreateTime();
    }

    @Override
    public void insertBatch() {
        //redis 中的数据数量
        Long logNums = redisTemplate.opsForSet().size(Constant.FLOW_ANALYSIS_LOG);
        Object flowAnalysisLogObject = redisTemplate.opsForSet().pop(Constant.FLOW_ANALYSIS_LOG,logNums);
        List<FlowLog> flowLogList = (List<FlowLog>) flowAnalysisLogObject;
        //按照uuidSession进行分组,分别统计每个用户的操作行为
        Map<String, List<FlowLog>> flowLogAsUidSession = flowLogList.stream().collect(Collectors.groupingBy(FlowLog::getUuidSession));
        List<FlowLog> newFlowLogList = Lists.newArrayList();
        for (String uuidSession:flowLogAsUidSession.keySet()){
            //按照用户操作的步数进行分组（相同步数必定属于同一个页面）
            Map<Integer, List<FlowLog>> flowLogAsStep = flowLogAsUidSession.get(uuidSession).stream().collect(Collectors.groupingBy(FlowLog::getStep));
            for (Integer step:flowLogAsStep.keySet()){
                List<FlowLog> flowLogListAsClick = Lists.newArrayList();
                List<FlowLog> flowLogListAsShopCat = Lists.newArrayList();
                for (FlowLog flowLog:flowLogAsStep.get(step)){
                    // 分享页面、访问页面
                    if (FlowVisitEnum.isVisitOrShare(flowLog.getVisitType())){
                        //页面访问直接添加到集合
                        flowLog.setNums(1);
                        newFlowLogList.add(flowLog);
                    }
                    // 点击页面
                    else if (FlowVisitEnum.CLICK.value().equals(flowLog.getVisitType())){
                        //页面点击操作添加到新的集合，统计操作的数量（多条相同的操作数据统计为一条），减少插入的数据数量
                        flowLogListAsClick.add(flowLog);
                    }
                    // 加购
                    else {
                        //同上
                        flowLogListAsShopCat.add(flowLog);
                    }
                }
                if (flowLogListAsClick.size() > 0){
                    //对页面点击数据处理，统计操作的数量（多条相同的操作数据统计为一条）
                    newFlowLogList.addAll(getClickOrShopCatFlowLog(flowLogListAsClick, Boolean.FALSE));
                }
                if (flowLogListAsShopCat.size() > 0){
                    //对加购数据处理，统计操作的数量（多条相同的操作数据统计为一条）
                    newFlowLogList.addAll(getClickOrShopCatFlowLog(flowLogListAsShopCat, Boolean.TRUE));
                }
            }
        }
        // 数据保存到数据库中
        saveBatch(newFlowLogList);
        //当天的数据写入缓存中，用于实时数据的统计
        List<FlowLog> flowLogListCurrent = Lists.newArrayList();
        Long currentTime = DateUtil.beginOfDay(new Date()).getTime();
        for (FlowLog flowLog:newFlowLogList){
            if (flowLog.getCreateTime().getTime() >= currentTime){
                flowLog.setHour(DateUtil.hour(flowLog.getCreateTime(), true));
                flowLogListCurrent.add(flowLog);
            }
        }
        List<FlowLog> flowLogListCache = cacheManagerUtil.getCache(CACHE_NAME, CACHE_KEY);
        if (Objects.nonNull(flowLogListCache) && !Objects.equals(flowLogListCache.size(),0)){
            FlowLog flowLog = flowLogListCache.get(flowLogListCache.size()-1);
            //当天开始时间大于记录时间（代表新的一天），更新数据
            if (currentTime > flowLog.getCreateTime().getTime()){
                flowLogListCache =  flowLogListCurrent;
            }else {
                flowLogListCache.addAll(flowLogListCurrent);
            }
        }else {
            flowLogListCache =  flowLogListCurrent;
        }
        flowLogListCache.sort(Comparator.comparing(FlowLog::getCreateTime));
        flowLogPutCache(flowLogListCache);
    }

    /**
     *  获取用户点击或加购的数量
     * @param flowLogList 源数据
     * @param isShopCat 是否是加购操作
     * @return 统计后的用户操作记录
     */
    private List<FlowLog> getClickOrShopCatFlowLog(List<FlowLog> flowLogList, Boolean isShopCat){
        List<FlowLog> newFlowLogList = Lists.newArrayList();
        FlowLog flowLog = flowLogList.get(0);

        if (!FlowLogPageEnum.isProdInfo(flowLog.getPageId())){
            //非商品详情页
            flowLog.setNums(flowLogList.size());
            newFlowLogList.add(flowLog);
        }else {
            //商品详情页
            //根据BizData（prodId）进行分组
            Map<String, List<FlowLog>> flowLogListAsBizData = flowLogList.stream().filter(f-> StrUtil.isNotBlank(f.getBizData())).collect(Collectors.groupingBy(FlowLog::getBizData));
            List<FlowLog> flowLogListAsProdId = null;
            FlowLog flowLogAsProdId = null;
            //统计该商品相同行为的所有操作数据量
            for (String prodId:flowLogListAsBizData.keySet()){
                flowLogListAsProdId = flowLogListAsBizData.get(prodId);
                flowLogAsProdId = flowLogListAsProdId.get(0);
                if (isShopCat) {
                    flowLogAsProdId.setNums(flowLogListAsProdId.stream().mapToInt(FlowLog::getNums).sum());
                } else {
                    flowLogAsProdId.setNums(flowLogListAsProdId.size());
                }
                newFlowLogList.add(flowLogAsProdId);
            }
        }
        return newFlowLogList;
    }
    @Override
    public List<FlowLog> getFlowLogListByCache(){
        Date date = DateUtil.beginOfDay(new Date());
        //获取缓存
        insertBatch();
        List<FlowLog> flowLogList = cacheManagerUtil.getCache(CACHE_NAME, CACHE_KEY);
        Integer count  = flowLogMapper.getFlowLogCountByDate(date);
        if (count > flowLogList.size()){
            flowLogList = flowLogMapper.getFlowLogByDate(date,null);
            for (FlowLog flowLog :flowLogList){
                flowLog.setHour(DateUtil.hour(flowLog.getCreateTime(),true));
            }
            flowLogPutCache(flowLogList);
        }
        return flowLogList;
    }

    @Override
    public void flowLogPutCache(List<FlowLog> flowLogList){
        cacheManagerUtil.putCache(CACHE_NAME, CACHE_KEY, flowLogList);
    }

    @Override
    public List<FlowLog> getFlowLogByDate(Date startTime, Date endTime) {
        return flowLogMapper.getFlowLogByDate(startTime,endTime);
    }

    @Override
    public void deleteAllByDate(Date startTime, Date endTime) {
        flowLogMapper.deleteAllByDate(startTime,endTime);
    }

    @Override
    public Long countVisitProdNum(ProdAnalysisSurveyParam param) {
        return flowLogMapper.countVisitProdNum(param);
    }

    @Override
    public Long countVisitProdInfoNum(ProdAnalysisSurveyParam param) {
        return flowLogMapper.countVisitProdInfoNum(param);
    }

    @Override
    public Long countProdExposeNum(ProdAnalysisSurveyParam param) {
        return flowLogMapper.countProdExposeNum(param);
    }

    @Override
    public Long countProdVisitorNum(ProdAnalysisSurveyParam param) {
        return flowLogMapper.countProdVisitorNum(param);
    }

    @Override
    public Long countAddCartProdNum(ProdAnalysisSurveyParam param) {
        Long num = flowLogMapper.countAddCartProdNum(param);
        if (Objects.isNull(num)) {
            return 0L;
        }
        return num;
    }

    @Override
    public Long countShareVisit(ProdAnalysisSurveyParam param) {
        return flowLogMapper.countShareVisit(param);
    }

    @Override
    public Integer countAllVisitor(Date date, Date startTime, Date endTime) {
        return flowLogMapper.countAllVisitor(date, startTime, endTime);
    }

    @Override
    public Integer countAddCartUserNum(Date startTime, Date endTime) {
        return flowLogMapper.countAddCartUserNum(startTime, endTime);
    }

    @Override
    public Integer countUserNum(Date startTime, Date endTime) {
        return flowLogMapper.countUserNum(startTime, endTime);
    }

    @Override
    public void saveUserFlowLog(FlowLog flowLog, HttpServletRequest request) {
        flowLog.setCreateTime(new Date());
        flowLog.setIp(getIp(request));
        // 只插入数据格式正确的数据
        if (verifyData(flowLog)) {
            // 记录存入缓存中
            redisTemplate.opsForSet().add(Constant.FLOW_ANALYSIS_LOG, flowLog);
            int flowMaxSize = 5000;
            // 没有开启定时任务，或者缓存数据量过大时，提交一次数据
            if (redisTemplate.opsForSet().size(Constant.FLOW_ANALYSIS_LOG) > flowMaxSize) {
                insertBatch();
            }
        }
    }

    /**
     * 获取用户ip
     */
    @Override
    public String getIp(HttpServletRequest request) {
        String ipAddress;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (LOCALHOST.equals(ipAddress)) {
                    // 根据网卡取本机配置的IP
                    try {
                        ipAddress = InetAddress.getLocalHost().getHostAddress();
                    } catch (UnknownHostException e) {
                        log.error("获取用户ip错误:", e);
                    }
                }
            }
            // 通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null) {
                if (ipAddress.contains(StrUtil.COMMA)) {
                    return ipAddress.split(StrUtil.COMMA)[0];
                } else {
                    return ipAddress;
                }
            } else {
                return "";
            }
        } catch (Exception e) {
            log.error("获取用户ip错误:", e);
            return "";
        }
    }

    @Override
    public ProdAnalysisDataParam getProdSurvey(ProdAnalysisSurveyParam param) {
        return flowLogMapper.getProdSurvey(param);
    }


    private boolean verifyData(FlowLog flowLog) {
        // 数据为空，打印日志
        if (Objects.isNull(flowLog.getPageId()) || Objects.isNull(flowLog.getUuid()) || Objects.isNull(flowLog.getUuidSession()) ||
                Objects.isNull(flowLog.getVisitType()) || Objects.isNull(flowLog.getStep()) || Objects.isNull(flowLog.getSystemType())) {
            log.info("用户流量记录数据为空：" + flowLog.toString());
            return false;
        }
        // 商品id格式错误
        if (FlowLogPageEnum.isProdInfo(flowLog.getPageId())) {
            Matcher isNum = PATTERN.matcher(flowLog.getBizData());
            return !StrUtil.isBlank(flowLog.getBizData()) && isNum.matches();
        }
        return true;
    }


}
